package com.chinaedu.oc.util.plugin;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;

@Intercepts({
	@Signature(type = StatementHandler.class,
			method = "prepare",
			args = {Connection.class})
})
public class PagePlugin implements Interceptor {

	private Integer defaultPage;
	private Integer defaultPageSize;
	private Boolean defaultUseFlag;
	private Boolean defaultCheckFlag;
	
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler stmtHandler = getUnProxyObject(invocation);
		MetaObject metaStatementHandler = SystemMetaObject.forObject(stmtHandler);
		
		String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		//不是select语句
		if(!checkSelect(sql)) {
			return invocation.proceed();
		}
		
		BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
		Object parameterObject = boundSql.getParameterObject();
		PageParams pageParams = getPageParams(parameterObject);
		//没有分页参数，不启用插件
		if(pageParams == null) {
			return invocation.proceed();
		}
		//获取分页参数，获取不到时候使用默认值
		Integer pageNum = pageParams.getPage() == null ? 
					this.defaultPage : pageParams.getPage();
		Integer pageSize = pageParams.getPageSize() == null ?
					this.defaultPageSize : pageParams.getPageSize();
		Boolean useFlag = pageParams.getUseFlag() == null ?
					this.defaultUseFlag : pageParams.getUseFlag();
		Boolean checkFlag = pageParams.getCheckFlag() == null ?
					this.defaultCheckFlag : pageParams.getCheckFlag();
		//不适用分页插件
		if(!useFlag || pageParams.getPage()==null || pageParams.getPageSize() == null) {
			return invocation.proceed();
		}
		
		int total = getTotal(invocation, metaStatementHandler, boundSql);
		//回填总数到分页参数里
		setTotalToPageParams(pageParams, total, pageSize);
		//检查当前页码的有效性
		checkPage(checkFlag, pageNum, pageParams.getTotalPage());
		//修改sql
		return changeSql(invocation, metaStatementHandler, boundSql, pageNum, pageSize);
	}
	@Override
	public Object plugin(Object statementHandler) {
		return Plugin.wrap(statementHandler, this);
	}

	@Override
	public void setProperties(Properties props) {
		String strDefaultPage = props.getProperty("default.page", "1");
		String strDefaultPageSize = props.getProperty("default.pageSize", "50");
		String strDefaultUseFlag = props.getProperty("default.useFlag", "false");
		String strDefaultCheckFlag = props.getProperty("default.checkFlag", "false");
		
		this.defaultPage = Integer.parseInt(strDefaultPage);
		this.defaultPageSize = Integer.parseInt(strDefaultPageSize);
		this.defaultUseFlag = Boolean.parseBoolean(strDefaultUseFlag);
		this.defaultCheckFlag = Boolean.parseBoolean(strDefaultCheckFlag);

	}
	
	private StatementHandler getUnProxyObject(Invocation ivt) {
		StatementHandler statementHandler = (StatementHandler) ivt.getTarget();
		MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
		Object object = null;
		while(metaStatementHandler.hasGetter("h")) {
			object = metaStatementHandler.getValue("h");
		}
		if(object == null) {
			return statementHandler;
		}
		return (StatementHandler) object;
	}
	
	private boolean checkSelect(String sql) {
		String trimSql = sql.trim();
		int idx = trimSql.toLowerCase().indexOf("select");
		
		return idx == 0;
	}
	/*
	 * **********分解分页参数，这里支持使用Map和@Param注解传递参数，或者pojo继承PageParams，这三种方式都可以
	 * 只拦截入参是PageParams的
	 */
	private PageParams getPageParams(Object parameterObject) {
		if(parameterObject == null) {
			return null;
		}
		PageParams pageParams = null;
//		if(parameterObject instanceof Map) {
//			Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
//			Set<String> keySet = paramMap.keySet();
//			Iterator<String> it = keySet.iterator();
//			while(it.hasNext()) {
//				String key = it.next();
//				Object value = paramMap.get(key);
//				if(value instanceof PageParams) {
//					return (PageParams) value;
//				}
//			}
//		}else if(parameterObject instanceof PageParams) {
//			pageParams = (PageParams) parameterObject;
//		}
		if(parameterObject instanceof PageParams) {
			pageParams = (PageParams) parameterObject;
		}
		
		return pageParams;
		
	}
	
	private int getTotal(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql) throws SQLException {
		MappedStatement mappedStatement = (MappedStatement) 
				metaStatementHandler.getValue("delegate.mappedStatement");
		Configuration cfg = mappedStatement.getConfiguration();
		String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		String countSql = "select count(*) as total from ("+sql+") $_table_a";
		Connection connection  = (Connection) invocation.getArgs()[0];
		PreparedStatement ps = null;
		int total = 0;
		try {
			ps = connection.prepareStatement(countSql);
			BoundSql countBoundSql = new BoundSql(cfg, countSql, 
					boundSql.getParameterMappings(), 
					boundSql.getParameterObject());
			ParameterHandler handler = new DefaultParameterHandler(mappedStatement, 
					boundSql.getParameterObject(), countBoundSql);
			handler.setParameters(ps);
			ResultSet rs = ps.executeQuery();
			while(rs.next()) {
				total = rs.getInt("total");
			}
		}finally {
			if(ps != null) {
				ps.close();
			}
		}
		return total;
	}
	private void setTotalToPageParams(PageParams pageParams, int total, Integer pageSize) {
		pageParams.setTotal(total);
		if(pageSize != null ) {
			int totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize +1;
			pageParams.setTotalPage(totalPage);
		}
	}
	private void checkPage(Boolean checkFlag, Integer pageNum, Integer totalPage) throws Exception {
		if(checkFlag && pageNum != null) {
			if(pageNum > totalPage) {
				//throw new Exception("查询时报，查询页码【"+pageNum+"】大于总页数【"+totalPage+"】！");
				pageNum = totalPage;
			}
		}
		
	}
	private Object changeSql(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql, Integer pageNum,
			Integer pageSize) throws Exception {
		String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		
		String newSql = "select $_table_a.* from ("+sql+") $_table_a ";
		if(pageSize != null) {
			newSql += "limit ?, ?";
		}
		metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
		PreparedStatement ps = (PreparedStatement) invocation.proceed();
		int count = ps.getParameterMetaData().getParameterCount();
		if(pageSize != null) {
			ps.setInt(count-1, (pageNum-1)*pageSize);
			ps.setInt(count, pageSize);
		}
		return ps;
	}
}
